package org.redmaple.core.template.dao.hibernate;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.redmaple.core.retrieving.RedmapleCriteria;
import org.redmaple.core.retrieving.RedmapleCriterion;
import org.redmaple.core.retrieving.Sort;
import org.redmaple.core.retrieving.hibernate.CriterionPlugin;
import org.redmaple.core.retrieving.hibernate.OrderPlugin;
import org.redmaple.core.support.Page;
import org.redmaple.core.support.PageHead;
import org.redmaple.core.template.dao.IPageDao;
import org.redmaple.core.template.model.BaseModel;
import org.redmaple.core.util.Reflections;
import org.springframework.util.CollectionUtils;

/**
 * 实现分页的一般化的DAO基类
 * 
 * @author Xiangyun
 *
 * @param <T>
 */
public abstract class PageDaoImpl<T extends BaseModel> extends
		GenericDaoImpl<T> implements IPageDao<T> {

	public Page<T> findPageByCondition(RedmapleCriteria redmapleCriteria,
			List<Sort> sortValues, PageHead pageInfo) {
		DetachedCriteria criteria = getBaseCriteria();
		List<RedmapleCriterion> listCriterions = redmapleCriteria
				.getListCriterions();
		for (RedmapleCriterion criterion : listCriterions) {
			criteria.add(CriterionPlugin.convert(criterion, getObjectName()));
		}
		if (CollectionUtils.isEmpty(sortValues)) {
			for (Sort sort : sortValues) {
				criteria.addOrder(OrderPlugin.convert(sort, getObjectName()));
			}
		}
		return findPageByCriteria(pageInfo, criteria);
	}

	/**
	 * 获取分页数据，不能由外部调用
	 * 
	 * @param pageInfo
	 *            当前页信息
	 * @param detachedCriteria
	 *            查询条件
	 * @return 页数据
	 */
	@SuppressWarnings("unchecked")
	protected Page<T> findPageByCriteria(final PageHead pageInfo,
			final DetachedCriteria detachedCriteria) {
		Page<T> page = null;
		if (pageInfo instanceof Page) {
			page = (Page<T>) pageInfo;
		} else {
			page = new Page<T>();
		}
		// 查询总行数
		long totalRows = pageInfo.getTotalRecords();
		if (totalRows == 0) {
			Criteria criteria = detachedCriteria
					.getExecutableCriteria(getSession());
			totalRows = countCriteriaResult(criteria);
			page.setTotalRecords(totalRows);
		}
		if (totalRows != 0) {
			int rowStartIndex = (pageInfo.getPageNo() - 1)
					* pageInfo.getPageSize();
			int rowEndIndex = (pageInfo.getPageNo()) * pageInfo.getPageSize();
			if (totalRows < rowStartIndex) {
				return page;
			}
			if (totalRows < rowEndIndex) {
				rowEndIndex = (int) totalRows;
			}
			List<T> listRet = findListByCriteria(detachedCriteria,
					rowStartIndex, rowEndIndex);
			page.setResult(listRet);
			return page;
		}
		return page;
	}

	/**
	 * 统计在没有页的限制下，一共有多少条符合条件的数据
	 * 
	 * @param criteria
	 *            查询条件
	 * @return 条符合条件的数据数目
	 */
	@SuppressWarnings("unchecked")
	protected long countCriteriaResult(final Criteria criteria) {
		CriteriaImpl impl = (CriteriaImpl) criteria;

		// 先把Projection、ResultTransformer、OrderBy取出来,清空三者后再执行Count操作
		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();
		List<CriteriaImpl.OrderEntry> orderEntries = (List<CriteriaImpl.OrderEntry>) Reflections
				.getFieldValue(impl, "orderEntries");
		Reflections.setFieldValue(impl, "orderEntries",
				new ArrayList<CriteriaImpl.OrderEntry>());

		// 执行Count查询
		Long totalCountObject = (Long) criteria.setProjection(
				Projections.rowCount()).uniqueResult();
		long totalCount = (totalCountObject != null) ? totalCountObject : 0;

		// 将之前的Projection,ResultTransformer和OrderBy条件重新设回去
		criteria.setProjection(projection);

		if (projection == null) {
			criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}
		if (transformer != null) {
			criteria.setResultTransformer(transformer);
		}
		Reflections.setFieldValue(impl, "orderEntries", orderEntries);

		return totalCount;
	}

}
